/*
 * ModelCC, distributed under ModelCC Shared Software License, www.modelcc.org
 */

package org.modelcc.language.syntax;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.modelcc.language.LanguageException;
import org.modelcc.language.NullElementException;
import org.modelcc.language.metamodel.LanguageModel;

/**
 * Grammar factory.
 * 
 * @author Luis Quesada (lquesada@modelcc.org)
 */
public final class GrammarFactory implements Serializable 
{
	private Grammar grammar;
	
    /**
     * Set of not empty objects.
     */
    private Set<Object> notEmpty;

    
    /**
     * Default constructor.
     */
    public GrammarFactory() 
    {
    	grammar = new Grammar();
        notEmpty = new HashSet<Object>();
    }

    /**
     * Adds a rule.
     * @param r the rule to be added.
     */
    public void addRule(Rule r) 
    {
    	grammar.addRule(r);
    }

    /**
     * Removes a rule.
     * @param r the rule to be removed.
     */
    public void removeRule(Rule r) 
    {
    	grammar.removeRule(r);
    }

    /**
     * Adds a rule.
     * @param r the rule to add.
     */
    public void addNotEmpty(Object object) {
         notEmpty.add(object);
    }

    /**
     * @param startType the start type to set
     */
    public void setStartType(Object startType) 
    {
    	grammar.setStartType(startType);
    }

    /**
     * @param sb the token symbol builder.
     */
    public void setTokenSymbolBuilder(SymbolBuilder sb) 
    {
    	grammar.setTokenSymbolBuilder(sb);
    }

    /**
     * Create a grammar.
     * @throws NullElementException whenever a rule element is null
     * @return the grammar.
     */
    public Grammar create() 
    	throws LanguageException 
    {
    	return create(null);
    }
    
    public Grammar create (LanguageModel model) 
    	throws LanguageException 
    {
    	grammar.setModel(model);
    	
        checkNullRuleElements();
        checkEmptyRules();
        
        grammar.prepare();

        return grammar;
    }

    // Check empty rules.

	private void checkEmptyRules() 
	{
        Map<Object,Set<Object>> emptyRules = new HashMap<Object,Set<Object>>();
        Map<Object,Rule> emptyRuleMap = new HashMap<Object,Rule>();
		
        Iterator<Rule> iter;
		Iterator<RuleSymbol> itee;
		RuleSymbol e;
		Rule r;
		
		Set<Rule> rules = grammar.getRules();

		for (iter = rules.iterator();iter.hasNext();) {
			r = iter.next();
			if (r.getRight().isEmpty()) {
				emptyRules.put(r.getLeft().getType(),null);
				emptyRuleMap.put(r.getLeft().getType(),r);
				iter.remove();
			}
		}

		boolean found;
		Set<Rule> rulesCopy = new HashSet<Rule>();
		for (iter = rules.iterator();iter.hasNext();) {
			r = iter.next();
			List<RuleSymbol> eles = new ArrayList<RuleSymbol>();
			eles.addAll(r.getRight());
			rulesCopy.add(new Rule(r));
		}
		boolean single; 
		do {
			found = false;
			for (iter = rulesCopy.iterator();iter.hasNext();) {
				r = iter.next();
				single = (r.getRight().size()==1);
				for (itee = r.getRight().iterator();itee.hasNext();) {
					e = itee.next();
					if (emptyRules.containsKey(e.getType())) {
						itee.remove();
						if (r.getRight().isEmpty()) {
							found = true;
							if (single) {
								Set<Object> singles = emptyRules.get(r.getLeft().getType());
								if (singles==null) {
									singles = new HashSet<Object>();
									emptyRules.put(r.getLeft().getType(),singles);
								}
								singles.add(e.getType());
							} else if (!emptyRules.containsKey(r.getLeft().getType()))
								emptyRules.put(r.getLeft().getType(),null);
							emptyRuleMap.put(r.getLeft().getType(),r);
						}
					}
				}
			}
		} while (found);

		// workaround
		for (Object o : notEmpty) {
			emptyRuleMap.remove(o);
			emptyRules.remove(o);
		}
		
        grammar.setEmptyRules(emptyRules, emptyRuleMap);		
	}

    // Check rule null elements.

	private void checkNullRuleElements()
			throws NullElementException 
	{
        for (Rule r: grammar.getRules()) {

			if (r.getLeft() == null) 
				throw new NullElementException("Null rule LHS exception: "+ r);
			
			if (r.getRight() == null)
				throw new NullElementException("Null rule RHS exception: "+ r);

			for (RuleSymbol elem: r.getRight()) {
				if (elem == null)
					throw new NullElementException("Null rule element exception: "+ r);
			}
		}
	}
}
